안녕하세요, 분산 시스템과 웹 서비스 최적화에 관심 많은 개발자 여러분!
현대의 웹 서비스는 사용자에게 끊김 없는 경험과 빠른 응답 속도를 제공해야 합니다. 하지만 모든 작업을 즉시, 동기적으로 처리하는 방식은 서비스의 확장성을 저해하고, 특정 작업이 지연될 경우 전체 시스템의 병목 현상을 유발할 수 있습니다. 이러한 문제를 해결하고 더욱 견고하며 확장 가능한 아키텍처를 구축하기 위한 핵심 전략 중 하나가 바로 ‘메시지 큐(Message Queue, MQ)’를 활용한 비동기 처리입니다.
오늘은 MQ가 비동기 처리에 어떻게 기여하며, 왜 현대 웹 개발에서 필수적인 기술로 자리 잡았는지 함께 알아보겠습니다.
기존의 동기 처리 방식은 요청이 들어오면 해당 작업이 완료될 때까지 다음 작업을 기다려야 합니다. 이는 마치 식당에서 한 손님의 주문이 완료될 때까지 다른 손님의 주문을 받지 못하는 것과 같습니다. 반면 비동기 처리는 오래 걸리는 작업을 백그라운드에서 처리하도록 맡겨두고, 그동안 다른 작업을 계속 진행할 수 있게 합니다.
비동기 처리의 주요 이점:
MQ는 독립적인 애플리케이션 컴포넌트 간에 메시지를 주고받을 수 있도록 지원하는 미들웨어입니다. 생산자(Producer)는 메시지를 큐에 발행하고, 소비자(Consumer)는 큐에서 메시지를 가져와 처리합니다. MQ가 비동기 처리에서 강력한 이유들은 다음과 같습니다.
실제 웹 서비스에서 MQ를 활용한 비동기 처리는 다양한 곳에서 빛을 발합니다.
pika 가정)생산자 (Producer) 코드:
import pika
import json
# RabbitMQ 연결 설정
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# 큐 선언 (없으면 생성)
channel.queue_declare(queue='task_queue', durable=True) # durable=True: 서버 재시작 시 큐 보존
def send_task(task_data):
message = json.dumps(task_data)
channel.basic_publish(
exchange='',
routing_key='task_queue',
body=message,
properties=pika.BasicProperties(
delivery_mode=2, # 메시지 지속성 (persistent)
))
print(f" [x] Sent '{task_data}'")
if __name__ == '__main__':
send_task({'type': 'email', 'to': 'user@example.com', 'subject': 'Welcome!'})
send_task({'type': 'image_process', 'path': '/uploads/image1.jpg', 'size': 'thumbnail'})
connection.close()
소비자 (Consumer) 코드:
import pika
import json
import time
# RabbitMQ 연결 설정
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# 큐 선언 (없으면 생성)
channel.queue_declare(queue='task_queue', durable=True)
print(' [*] Waiting for messages. To exit press CTRL+C')
def callback(ch, method, properties, body):
task = json.loads(body)
print(f" [x] Received '{task}'")
# 실제 작업 처리 로직
if task['type'] == 'email':
print(f" Processing email to {task['to']}")
time.sleep(5) # 이메일 발송에 5초 걸린다고 가정
elif task['type'] == 'image_process':
print(f" Processing image {task['path']}")
time.sleep(10) # 이미지 처리에 10초 걸린다고 가정
print(f" [x] Done processing '{task}'")
ch.basic_ack(delivery_tag=method.delivery_tag) # 작업 완료 후 메시지 승인
# 한 번에 하나의 메시지만 가져오도록 설정 (ack 전까지)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='task_queue', on_message_callback=callback)
channel.start_consuming()
위 예시에서 생산자는 작업을 큐에 발행하고 즉시 다른 작업을 진행합니다. 소비자는 큐에서 작업을 가져와 비동기적으로 처리하며, 작업이 완료된 후에만 큐에 ‘완료’ 신호를 보냅니다. 이를 통해 메인 애플리케이션의 지연 없이 백그라운드에서 복잡한 작업들이 안정적으로 수행될 수 있습니다.
메시지 큐를 활용한 비동기 처리는 현대 분산 시스템과 웹 서비스 구축에 있어 선택이 아닌 필수 전략입니다. 시스템 컴포넌트 간의 결합도를 낮추고, 높은 트래픽 속에서도 안정적인 서비스를 유지하며, 무한한 확장 가능성을 제공합니다.
여러분도 이메일 발송, 이미지 처리, 대용량 데이터 처리 등 서비스의 특정 부분을 비동기 처리로 전환하여 사용자 경험을 향상시키고, 더욱 견고하며 확장 가능한 아키텍처를 구축해 보시기 바랍니다. MQ는 여러분의 서비스가 한 단계 더 도약하는 데 강력한 발판이 될 것입니다!
Text by Chaelin & Gemini. Photographs by Chaelin, Unsplash.